using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input.Touch;

namespace BlobSim
{
    static class Utils
    {
        public static Vector2 ReversedVector2(this Vector2 vector)
        {
            return new Vector2(vector.Y, vector.X);
        }

        public static float GetAngle(Vector2 v1, Vector2 v2)
        {
            v1.Normalize();
            v2.Normalize();
            var cos = Vector2.Dot(v1, v2);
            cos = MathHelper.Clamp(cos, -1.0f, 1.0f);
            return (float) Math.Acos(cos);
        }

        public static void BuildCube(float size, int segments, out VertexPositionNormalTexture[] vertices, out ushort[] indices)
        {
            int idx;
            Vector3 normal;

            vertices = new VertexPositionNormalTexture[6 * (segments + 1) * (segments + 1)];
            idx = 0;

            // NegativeX
            normal = new Vector3(-1.0f, 0.0f, 0.0f);
            for (int i = 0; i <= segments; i++)
            {
                for (int j = 0; j <= segments; j++)
                {
                    float u = i / (float)segments;
                    float v = j / (float)segments;

                    float x = (0.5f - u) * size;
                    float y = (0.5f - v) * size;

                    vertices[idx++] = new VertexPositionNormalTexture(new Vector3(-size / 2, y, x), normal,
                                                                      new Vector2(u, v));
                }
            }

            // PositiveX
            normal = new Vector3(1.0f, 0.0f, 0.0f);
            for (int i = 0; i <= segments; i++)
            {
                for (int j = 0; j <= segments; j++)
                {
                    float u = i / (float)segments;
                    float v = j / (float)segments;

                    float x = (0.5f - u) * size;
                    float y = (0.5f - v) * size;

                    vertices[idx++] = new VertexPositionNormalTexture(new Vector3(size / 2, y, -x), normal,
                                                                      new Vector2(u, v));
                }
            }

            // NegativeY
            normal = new Vector3(0.0f, -1.0f, 0.0f);
            for (int i = 0; i <= segments; i++)
            {
                for (int j = 0; j <= segments; j++)
                {
                    float u = i / (float)segments;
                    float v = j / (float)segments;

                    float x = (0.5f - u) * size;
                    float y = (0.5f - v) * size;

                    vertices[idx++] = new VertexPositionNormalTexture(new Vector3(-x, -size / 2, -y), normal,
                                                                      new Vector2(u, v));
                }
            }

            // PositiveY
            normal = new Vector3(0.0f, 1.0f, 0.0f);
            for (int i = 0; i <= segments; i++)
            {
                for (int j = 0; j <= segments; j++)
                {
                    float u = i / (float)segments;
                    float v = j / (float)segments;

                    float x = (0.5f - u) * size;
                    float y = (0.5f - v) * size;

                    vertices[idx++] = new VertexPositionNormalTexture(new Vector3(-x, size / 2, y), normal,
                                                                      new Vector2(u, v));
                }
            }

            // NegativeZ
            normal = new Vector3(0.0f, 0.0f, -1.0f);
            for (int i = 0; i <= segments; i++)
            {
                for (int j = 0; j <= segments; j++)
                {
                    float u = i / (float)segments;
                    float v = j / (float)segments;

                    float x = (0.5f - u) * size;
                    float y = (0.5f - v) * size;

                    vertices[idx++] = new VertexPositionNormalTexture(new Vector3(-x, y, -size / 2), normal,
                                                                      new Vector2(u, v));
                }
            }

            // PositiveZ
            normal = new Vector3(0.0f, 0.0f, 1.0f);
            for (int i = 0; i <= segments; i++)
            {
                for (int j = 0; j <= segments; j++)
                {
                    float u = i / (float)segments;
                    float v = j / (float)segments;

                    float x = (0.5f - u) * size;
                    float y = (0.5f - v) * size;

                    vertices[idx++] = new VertexPositionNormalTexture(new Vector3(x, y, size / 2), normal,
                                                                      new Vector2(u, v));
                }
            }

            // reset index iterator
            idx = 0;

            indices = new ushort[6 * segments * 2 * (segments + 1)];
            for (int f = 0; f < 6; f++) // iterate faces
            {
                int faceBaseVertexIdx = f * (segments + 1) * (segments + 1);
                for (int s = 0; s < segments; s++) // iterate strips
                {
                    for (int i = 0; i <= segments; i++) // iterate vertices
                    {
                        indices[idx++] = (ushort)(faceBaseVertexIdx + (s + 1) * (segments + 1) + i);
                        indices[idx++] = (ushort)(faceBaseVertexIdx + s * (segments + 1) + i);
                    }
                }
            }
        }

        public static TouchLocation GetPreviousTouchLocation(TouchLocation currentTouchLocation)
        {
            TouchLocation prevTouchLocation;
            if (!currentTouchLocation.TryGetPreviousLocation(out prevTouchLocation))
            {
                prevTouchLocation = currentTouchLocation;
            }
            return prevTouchLocation;
        }

        public static Matrix CalcLookAt(float horizontalAngle, float verticalAngle)
        {
            var target = new Vector3(
                (float) Math.Cos(horizontalAngle)*(float) Math.Cos(verticalAngle),
                (float) Math.Sin(verticalAngle),
                -(float) Math.Sin(horizontalAngle)*(float) Math.Cos(verticalAngle));

            return Matrix.CreateLookAt(Vector3.Zero, target, Vector3.Up);
        }

        public static void BuildSphere(float radius, int slices, int segments, 
            out VertexPositionNormalTexture[] vertices, out ushort[] indices)
        {
            vertices = new VertexPositionNormalTexture[(slices + 1)*(segments + 1)];
            int i,j;

            for (i = 0; i <= slices; i++)
            {
                for (j = 0; j <= segments; j++)
                {
                    float alpha = (i*MathHelper.TwoPi)/slices;
                    float beta = (j*MathHelper.Pi)/segments - MathHelper.PiOver2;

                    var u = i/(float) slices;
                    var v = j/(float) segments;

                    var cosAlpha = (float)Math.Cos(alpha);
                    var sinAlpha = (float)Math.Sin(alpha);
                    var cosBeta = (float)Math.Cos(beta);
                    var sinBeta = (float)Math.Sin(beta);

                    var x = cosAlpha * cosBeta;
                    var y = sinBeta;
                    var z = sinAlpha * cosBeta;

                    var n = new Vector3(x, y, z);

                    vertices[i*(segments+1) + j] = new VertexPositionNormalTexture(n*radius, n, new Vector2(u,v));
                }
            }

            indices = new ushort[2*slices*(segments + 1)];
            for (i = 0; i < slices; i++)
            {
                for (j = 0; j <= segments; j++)
                {
                    indices[2*(i*(segments + 1) + j)] = (ushort) (i*(segments + 1) + j);
                    indices[2*(i*(segments + 1) + j) + 1] = (ushort) ((i + 1)*(segments + 1) + j);
                }
            }
        }

        public static void BuildTorus(float innerRadius, float outerRadius, int slices, int segments,
            out VertexPositionNormalTexture[] vertices, out ushort[] indices)
        {
            var bigRadius = (outerRadius + innerRadius) / 2;
            var smallRadius = (outerRadius - innerRadius)/2;
            vertices = new VertexPositionNormalTexture[(slices + 1) * (segments + 1)];
            int i, j;

            for (i = 0; i <= slices; i++)
            {
                for (j = 0; j <= segments; j++)
                {
                    float alpha = (i * MathHelper.TwoPi) / slices;
                    float beta = (j * MathHelper.TwoPi) / segments;

                    var u = i / (float)slices;
                    var v = j / (float)segments;

                    var cosAlpha = (float)Math.Cos(alpha);
                    var sinAlpha = (float)Math.Sin(alpha);
                    var cosBeta = (float)Math.Cos(beta);
                    var sinBeta = (float)Math.Sin(beta);

                    var vx = (bigRadius + smallRadius*cosBeta)*cosAlpha;
                    var vy = smallRadius*sinBeta;
                    var vz = (bigRadius + smallRadius*cosBeta)*sinAlpha;

                    var nx = cosBeta*cosAlpha;
                    var ny = sinBeta;
                    var nz = cosBeta*sinAlpha;

                    vertices[i*(segments + 1) + j] = new VertexPositionNormalTexture(new Vector3(vx, vy, vz),
                                                                                     new Vector3(nx, ny, nz),
                                                                                     new Vector2(u, v));
                }
            }

            indices = new ushort[2 * slices * (segments + 1)];
            for (i = 0; i < slices; i++)
            {
                for (j = 0; j <= segments; j++)
                {
                    indices[2 * (i * (segments + 1) + j)] = (ushort)(i * (segments + 1) + j);
                    indices[2 * (i * (segments + 1) + j) + 1] = (ushort)((i + 1) * (segments + 1) + j);
                }
            }
        }

        public static void BuildCylinder(float radius, int slices, float height, int segments,
            out VertexPositionNormalTexture[] vertices, out ushort[] indices)
        {
            Vector3 normal;

            vertices = new VertexPositionNormalTexture[2 * slices + (segments + 1) * (slices + 1)];

            normal = new Vector3(0.0f, 1.0f, 0.0f);
            for (int i = 0; i < slices; i++)
            {
                float alpha = (i * MathHelper.TwoPi) / slices;

                float cosAlpha = (float)Math.Cos(alpha);
                float sinAlpha = (float)Math.Sin(alpha);

                float u = 0.5f * cosAlpha + 0.5f;
                float v = 0.5f * sinAlpha + 0.5f;

                float x = radius * cosAlpha;
                float y = height / 2;
                float z = radius * sinAlpha;

                var vertex = new Vector3(x, y, z);

                vertices[i] = new VertexPositionNormalTexture(vertex, normal, new Vector2(u, v));
            }

            normal = new Vector3(0.0f, -1.0f, 0.0f);
            for (int i = 0; i < slices; i++)
            {
                float alpha = (i * MathHelper.TwoPi) / slices;

                float cosAlpha = (float)Math.Cos(alpha);
                float sinAlpha = (float)Math.Sin(alpha);

                float u = 0.5f * cosAlpha + 0.5f;
                float v = 0.5f * sinAlpha + 0.5f;

                float x = radius * cosAlpha;
                float y = -height / 2;
                float z = radius * sinAlpha;

                var vertex = new Vector3(x, y, z);

                vertices[slices + i] = new VertexPositionNormalTexture(vertex, normal, new Vector2(u, v));
            }

            for (int i = 0; i <= slices; i++)
            {
                float alpha = (i * MathHelper.TwoPi) / slices;

                float cosAlpha = (float)Math.Cos(alpha);
                float sinAlpha = (float)Math.Sin(alpha);

                float x = radius * cosAlpha;
                float z = radius * sinAlpha;

                float u = i / (float)slices;

                var n = new Vector3(cosAlpha, 0, sinAlpha);

                for (int j = 0; j <= segments; j++)
                {
                    float v = j / (float)segments;

                    float y = height / 2 - height * v;

                    var vertex = new Vector3(x, y, z);

                    vertices[2 * slices + j * (slices + 1) + i] = new VertexPositionNormalTexture(vertex, n, new Vector2(u, v));
                }
            }

            indices = new ushort[2 * slices + 2 * (slices + 1)];

            indices[0] = 0;
            for (int i = 0; i < slices / 2; i++)
                indices[1 + 2 * i] = (ushort)(i + 1);
            for (int i = 0; i < (slices - 1) / 2; i++)
                indices[2 + 2 * i] = (ushort)(slices - i - 1);

            indices[slices] = 0;
            for (int i = 0; i < (slices - 1) / 2; i++)
                indices[slices + 2 + 2 * i] = (ushort)(i + 1);
            for (int i = 0; i < slices / 2; i++)
                indices[slices + 1 + 2 * i] = (ushort)(slices - i - 1);

            for (int i = 0; i <= slices; i++)
            {
                indices[2 * slices + 2 * i] = (ushort)i;
                indices[2 * slices + 2 * i + 1] = (ushort)((slices + 1) + i);
            }
        }
    }
}
